import { SonarQubeClient } from '../utils/SonarClient.js';
import { createLogger } from '../utils/Logger.js';
import { getGlobalOpsFile, getProjectOpsFile } from '../utils/OpsFile.js';
import { StrUtil } from '../utils/StrUtil.js';
import chalk from 'chalk';
import readline from 'readline';
import stringWidth from 'string-width';
import process from 'process';

const logger = createLogger('SONAR');

// 列宽配置
const COL_WIDTHS = {
    index: 5,
    name: 30,
    bugs: 10,
    coverage: 15,
    version: 50,
    // 详情页额外字段
    linesToCover: 15,
    uncoveredLines: 15,
    tests: 10,
    testFailures: 10,
    testSkipped: 10,
    // 文件列表字段
    file: 60,
    uncoveredConditions: 15,
    qualified: 12, // 是否达标
    analysisDate: 22,
    trendDate: 20
};

function formatAnalysisDate(dateStr) {
    if (!dateStr || typeof dateStr !== 'string') return '-';
    // 预期格式 2025-11-27T17:48:26+0800，转换为易读格式
    const formatted = dateStr.replace('T', ' ').replace(/([+-]\d{4}|Z)$/i, '').trim();
    return formatted || dateStr;
}

function formatTrendDate(dateStr, withTime = false) {
    if (!dateStr) return '-';
    const date = new Date(dateStr);
    if (Number.isNaN(date.getTime())) {
        return withTime ? dateStr : (dateStr.substring(5, 10) || dateStr);
    }
    const month = `${date.getMonth() + 1}`.padStart(2, '0');
    const day = `${date.getDate()}`.padStart(2, '0');
    if (!withTime) {
        return `${month}-${day}`;
    }
    const hour = `${date.getHours()}`.padStart(2, '0');
    const minute = `${date.getMinutes()}`.padStart(2, '0');
    return `${month}-${day} ${hour}:${minute}`;
}

function normalizeCoverageTrend(trend = [], limit = 15) {
    const normalized = trend
        .map(item => {
            const ts = new Date(item?.date).getTime();
            if (!item || !item.date || Number.isNaN(ts) || !Number.isFinite(item.value)) return null;
            return { date: item.date, value: item.value, ts };
        })
        .filter(Boolean)
        .sort((a, b) => b.ts - a.ts); // 时间降序

    const merged = [];
    const seenCoverage = new Set();
    for (const item of normalized) {
        const key = item.value.toFixed(1);
        if (seenCoverage.has(key)) continue;
        seenCoverage.add(key);
        merged.push({ date: item.date, value: item.value });
        if (merged.length >= limit) break;
    }

    return merged;
}

function pad(str, width) {
    const text = (str ?? '').toString();
    // 去掉ANSI序列再计算宽度 (简单处理)
    const visibleLength = stringWidth(text.replace(/\u001b\[\d+m/g, ''));

    if (visibleLength > width) {
        let truncated = '';
        let len = 0;
        for (const char of text) {
            const charWidth = stringWidth(char);
            if (len + charWidth > width - 1) break;
            truncated += char;
            len += charWidth;
        }
        return truncated + '…';
    }
    return text + ' '.repeat(width - visibleLength);
}

function printSeparator(columns) {
    const totalWidth = columns.reduce((sum, key) => sum + COL_WIDTHS[key], 0) + (columns.length - 1) * 2;
    console.log(chalk.gray('-'.repeat(totalWidth)));
}

function printRow(data, columns) {
    const line = columns.map(key => pad(data[key], COL_WIDTHS[key])).join('  ');
    console.log(line);
}

function printHeader(columns, labels) {
    printSeparator(columns);
    const headerData = {};
    columns.forEach(key => headerData[key] = labels[key]);
    const line = columns.map(key => pad(chalk.bold(labels[key]), COL_WIDTHS[key])).join('  ');
    console.log(line);
    printSeparator(columns);
}

async function resolveOpsName(appName, projectOpsFile) {
    logger.info(`开始解析应用 ${appName} 的 Ops 名称`);
    if (!projectOpsFile) {
        logger.info(`未找到项目级 ops 配置，直接使用原应用名: ${appName}`);
        return appName;
    }
    try {
        const primaryOpsName = await projectOpsFile.read(`flow.${appName}.opsName`);
        logger.info(`读取 flow.${appName}.opsName => ${primaryOpsName ?? '未命中'}`);
        if (StrUtil.notNull(primaryOpsName)) {
            logger.info(`解析结果: ${primaryOpsName}`);
            return primaryOpsName;
        }
    } catch (e) {
        logger.debug(`读取 flow.${appName}.opsName 失败: ${e.message}`);
    }
    try {
        const fallbackOpsName = await projectOpsFile.read(`flow_jz_plugin.${appName}.opsName`);
        logger.info(`读取 flow_jz_plugin.${appName}.opsName => ${fallbackOpsName ?? '未命中'}`);
        if (StrUtil.notNull(fallbackOpsName)) {
            logger.info(`解析结果: ${fallbackOpsName}`);
            return fallbackOpsName;
        }
    } catch (e) {
        logger.debug(`读取 flow_jz_plugin.${appName}.opsName 失败: ${e.message}`);
    }
    logger.info(`未找到映射，使用原应用名: ${appName}`);
    return appName;
}

async function fetchAppSummary(client, appName) {
    try {
        // 获取度量数据
        const measures = await client.getComponentMeasures(appName, ['bugs', 'coverage']);
        // 获取版本
        const version = await client.getProjectVersion(appName);

        let bugs = '0';
        let coverage = '0.0%';

        if (measures && measures.component && measures.component.measures) {
            const bugMeasure = measures.component.measures.find(m => m.metric === 'bugs');
            const covMeasure = measures.component.measures.find(m => m.metric === 'coverage');
            if (bugMeasure) bugs = bugMeasure.value;
            if (covMeasure) coverage = covMeasure.value + '%';
        }

        return {
            name: appName,
            bugs,
            coverage,
            version
        };
    } catch (e) {
        logger.error(`获取 ${appName} 数据失败: ${e.message}`);
        return {
            name: appName,
            bugs: '-',
            coverage: '-',
            version: '-'
        };
    }
}

async function showDetails(client, appName) {
    console.log(chalk.cyan(`\n正在获取 ${appName} 详细信息...`));

    try {
        // 1. 获取详细指标
        const metricKeys = [
            'bugs', 'coverage', 'lines_to_cover', 'uncovered_lines',
            'tests', 'test_failures', 'skipped_tests'
        ];
        const measuresData = await client.getComponentMeasures(appName, metricKeys);
        const version = await client.getProjectVersion(appName);

        const data = {
            name: appName,
            version,
            bugs: '0',
            coverage: '0.0%',
            linesToCover: '0',
            uncoveredLines: '0',
            tests: '0',
            testFailures: '0',
            testSkipped: '0'
        };

        if (measuresData && measuresData.component && measuresData.component.measures) {
            measuresData.component.measures.forEach(m => {
                switch (m.metric) {
                    case 'bugs': data.bugs = m.value; break;
                    case 'coverage': data.coverage = m.value + '%'; break;
                    case 'lines_to_cover': data.linesToCover = m.value; break;
                    case 'uncovered_lines': data.uncoveredLines = m.value; break;
                    case 'tests': data.tests = m.value; break;
                    case 'test_failures': data.testFailures = m.value; break;
                    case 'skipped_tests': data.testSkipped = m.value; break;
                }
            });
        }

        // 打印详细信息摘要
        console.log(chalk.bold(`\n📊 ${appName} 详细报告`));
        const summaryCols = ['name', 'bugs', 'coverage', 'version', 'linesToCover', 'uncoveredLines', 'tests', 'testFailures', 'testSkipped'];
        const summaryLabels = {
            name: '应用', bugs: 'Bug数', coverage: '覆盖率', version: '当前版本',
            linesToCover: '可覆盖行', uncoveredLines: '未覆盖行', tests: '单元测试数',
            testFailures: '失败数', testSkipped: '跳过数'
        };
        printHeader(summaryCols, summaryLabels);
        printRow(data, summaryCols);
        printSeparator(summaryCols);

        // 2. 获取文件列表
        console.log(chalk.cyan(`\n正在获取文件列表...`));
        const fileTree = await client.getComponentTree(appName, ['coverage', 'uncovered_lines', 'uncovered_conditions']);

        if (fileTree && fileTree.components) {
            const files = fileTree.components
                .filter(c => c.qualifier === 'FIL')
                .map(c => {
                    let coverage = '0.0%';
                    let uncoveredLines = '0';
                    let uncoveredConditions = '0';

                    c.measures.forEach(m => {
                        if (m.metric === 'coverage') coverage = m.value + '%';
                        if (m.metric === 'uncovered_lines') uncoveredLines = m.value;
                        if (m.metric === 'uncovered_conditions') uncoveredConditions = m.value;
                    });

                    return {
                        file: c.name || c.path,
                        coverage,
                        uncoveredLines,
                        uncoveredConditions
                    };
                })
                .sort((a, b) => parseFloat(a.coverage) - parseFloat(b.coverage)); // 按覆盖率升序排序

            console.log(chalk.bold(`\n📄 文件列表 (Top 50 按覆盖率升序)`));
            const fileCols = ['file', 'coverage', 'uncoveredLines', 'uncoveredConditions'];
            const fileLabels = {
                file: '类/文件', coverage: '覆盖率', uncoveredLines: '未覆盖行', uncoveredConditions: '未覆盖分支'
            };

            printHeader(fileCols, fileLabels);
            files.slice(0, 50).forEach(f => printRow(f, fileCols));
            printSeparator(fileCols);
            if (files.length > 50) {
                console.log(chalk.gray(`... 还有 ${files.length - 50} 个文件未显示`));
            }
        }

    } catch (e) {
        logger.error(`获取详情失败: ${e.message}`);
    }
}

/**
 * 显示未覆盖的代码列表（接口五）
 * @param {SonarQubeClient} client 
 * @param {string} componentKey 
 * @param {number} pageSize 每页显示的条数，默认30
 */
async function showUncoveredCode(client, componentKey, pageSize = 30) {
    try {
        logger.info(`获取 ${componentKey} 的详细信息和未覆盖代码列表...`);
        logger.info(`每页显示 ${pageSize} 条记录`);

        const analysisInfo = {
            version: '-',
            analysisDate: '-'
        };

        try {
            const navigationData = await client.getComponentNavigation(componentKey);
            if (navigationData) {
                analysisInfo.version = navigationData.version || '-';
                analysisInfo.analysisDate = formatAnalysisDate(navigationData.analysisDate);
            }
        } catch (e) {
            logger.warn(`获取打包信息失败: ${e.message}`);
        }

        // 1. 获取详细报告数据
        const metricKeys = [
            'bugs', 'coverage', 'lines_to_cover', 'uncovered_lines',
            'tests', 'test_failures', 'skipped_tests'
        ];
        const measuresData = await client.getComponentMeasures(componentKey, metricKeys);
        const version = await client.getProjectVersion(componentKey);

        const summaryData = {
            name: componentKey,
            version,
            analysisDate: analysisInfo.analysisDate,
            bugs: '0',
            coverage: '0.0%',
            linesToCover: '0',
            uncoveredLines: '0',
            tests: '0',
            testFailures: '0',
            testSkipped: '0'
        };

        let coverageTrendRaw = [];
        try {
            coverageTrendRaw = await client.getCoverageHistory(componentKey, 30, 15);
        } catch (e) {
            logger.warn(`获取覆盖率历史失败: ${e.message}`);
        }
        const coverageTrend = normalizeCoverageTrend(coverageTrendRaw, 15);

        if (measuresData && measuresData.component && measuresData.component.measures) {
            measuresData.component.measures.forEach(m => {
                switch (m.metric) {
                    case 'bugs': summaryData.bugs = m.value; break;
                    case 'coverage': summaryData.coverage = m.value + '%'; break;
                    case 'lines_to_cover': summaryData.linesToCover = m.value; break;
                    case 'uncovered_lines': summaryData.uncoveredLines = m.value; break;
                    case 'tests': summaryData.tests = m.value; break;
                    case 'test_failures': summaryData.testFailures = m.value; break;
                    case 'skipped_tests': summaryData.testSkipped = m.value; break;
                }
            });
        }

        // 2. 调用接口五：获取未覆盖代码，按 uncovered_lines 降序排序
        const params = new URLSearchParams({
            additionalFields: 'metrics',
            ps: '500',
            asc: 'false',
            metricSort: 'uncovered_lines',
            s: 'metric',
            metricSortFilter: 'withMeasuresOnly',
            component: componentKey,
            metricKeys: 'uncovered_lines,line_coverage',
            strategy: 'leaves'
        });

        const headers = {
            'Referer': `${client.baseUrl}/component_measures?id=${componentKey}&metric=uncovered_lines&view=list`
        };

        const data = await client.request(`/api/measures/component_tree?${params.toString()}`, 'GET', null, headers);

        if (!data || !data.components || data.components.length === 0) {
            logger.info('未找到任何文件数据');
            return;
        }

        // 解析并格式化数据
        const files = data.components.map(comp => {
            const measures = {};
            if (comp.measures) {
                comp.measures.forEach(m => {
                    measures[m.metric] = m.value;
                });
            }

            const coverage = parseFloat(measures.line_coverage || '0');
            const uncoveredLines = parseInt(measures.uncovered_lines || '0', 10);
            const qualified = coverage >= 50; // 是否达标

            return {
                name: comp.name || comp.path, // 使用 name 作为类名
                path: comp.path,
                coverage: coverage.toFixed(1),
                uncoveredLines,
                qualified
            };
        });

        // 显示详细报告的函数
        const showSummaryReport = () => {
            console.log(chalk.cyan.bold(`\n========== ${summaryData.name} 详细报告 ==========`));

            const sonarUrl = `https://sonarqube.hszq8.com/dashboard?id=${encodeURIComponent(summaryData.name)}`;
            const sonarLinkText = StrUtil.hyperlink('点击跳转 SonarQube 仪表盘', sonarUrl);
            console.log(chalk.yellow(`Sonar 地址: ${sonarLinkText}`));
            console.log(chalk.gray(`链接: ${sonarUrl}`));
            console.log();

            const summaryCols = ['bugs', 'coverage', 'version', 'analysisDate', 'linesToCover', 'uncoveredLines', 'tests', 'testFailures', 'testSkipped'];
            const summaryLabels = {
                bugs: 'Bug数',
                coverage: '覆盖率',
                version: '打包分支',
                analysisDate: '上次打包时间',
                linesToCover: '可覆盖行',
                uncoveredLines: '未覆盖行',
                tests: '单元测试数',
                testFailures: '测试失败数',
                testSkipped: '测试跳过数'
            };

            printHeader(summaryCols, summaryLabels);
            printRow(summaryData, summaryCols);
            printSeparator(summaryCols);
            console.log();

            const renderTrend = () => {
                console.log(chalk.bold('覆盖率趋势（最近30天，最多15个点）'));
                if (!coverageTrend || coverageTrend.length === 0) {
                    console.log(chalk.gray('暂无覆盖率历史数据'));
                    console.log();
                    return;
                }

                const values = coverageTrend.map(item => item.value);

                const min = Math.min(...values);
                const max = Math.max(...values);
                const latest = values[0]; // 当前数组按时间降序，首元素即最新
                const startLabel = formatTrendDate(coverageTrend[coverageTrend.length - 1].date);
                const endLabel = formatTrendDate(coverageTrend[0].date);

                console.log(chalk.gray(`范围: ${startLabel} ~ ${endLabel}（最新在前）`));
                console.log(chalk.gray(`最低: ${min.toFixed(1)}%  最高: ${max.toFixed(1)}%  最新: ${latest.toFixed(1)}%`));

                const trendCols = ['index', 'trendDate', 'coverage'];
                const trendLabels = {
                    index: '序号',
                    trendDate: '日期',
                    coverage: '覆盖率'
                };

                printHeader(trendCols, trendLabels);
                coverageTrend.forEach((item, idx) => {
                    const row = {
                        index: (idx + 1).toString(),
                        trendDate: formatTrendDate(item.date, true),
                        coverage: `${item.value.toFixed(1)}%`
                    };
                    printRow(row, trendCols);
                });
                printSeparator(trendCols);
                console.log();
            };

            renderTrend();
        };

        // 一次性展示未覆盖列表（最多100条）
        showSummaryReport();

        console.log(chalk.cyan.bold('--- 未覆盖代码列表 (最多 100 条，按覆盖率升序) -----'));
        console.log();

        const uncoveredCols = ['index', 'file', 'uncoveredLines', 'coverage', 'qualified'];
        const uncoveredLabels = {
            index: '序号',
            file: '类',
            uncoveredLines: '未覆盖的代码',
            coverage: '代码覆盖率(%)',
            qualified: '是否达标'
        };

        printHeader(uncoveredCols, uncoveredLabels);

        files.slice(0, 100).forEach((file, idx) => {
            const rowData = {
                index: (idx + 1).toString(),
                file: file.name, // 使用 name 字段
                uncoveredLines: file.uncoveredLines.toString(),
                coverage: file.coverage,
                qualified: file.qualified ? chalk.green('✓ 达标') : chalk.red('✗ 不达标')
            };
            printRow(rowData, uncoveredCols);
        });

        printSeparator(uncoveredCols);
        if (files.length > 100) {
            console.log(chalk.gray(`... 还有 ${files.length - 100} 个文件未显示`));
        }
        console.log(chalk.gray(`\n已展示 ${Math.min(files.length, 100)}/${files.length} 条记录`));

    } catch (e) {
        logger.error(`获取未覆盖代码失败: ${e.message}`);
    }
}

export async function execute(args = []) {
    // 解析选项和参数
    const options = {
        pageSize: 30  // 默认每页30条
    };

    // 拒绝旧的 -u 参数
    if (args.some(arg => arg === '-u' || arg === '--uncovered')) {
        logger.error('已移除 -u 选项，请直接执行: sonar <appName> [pageSize]');
        return;
    }

    const positionalArgs = args.filter(Boolean);

    if (positionalArgs.length === 0) {
        logger.error('请输入应用名，例如: sonar appName');
        logger.info('可选分页: sonar appName 50');
        return;
    }

    if (positionalArgs.length > 2) {
        logger.error('当前仅支持单个应用名和可选分页参数，请检查输入');
        return;
    }

    // 允许两种顺序：appName pageSize 或 pageSize appName
    let appArg = '';
    let pageArg = '';

    if (positionalArgs.length === 1) {
        appArg = positionalArgs[0];
    } else {
        const [first, second] = positionalArgs;
        const firstIsNum = /^\d+$/.test(first);
        const secondIsNum = /^\d+$/.test(second);

        if (firstIsNum && !secondIsNum) {
            pageArg = first;
            appArg = second;
        } else if (!firstIsNum && secondIsNum) {
            appArg = first;
            pageArg = second;
        } else {
            logger.error('分页参数需为正整数，命令示例: sonar appName 50');
            return;
        }
    }

    if (pageArg) {
        const pageSize = parseInt(pageArg, 10);
        if (pageSize <= 0) {
            logger.error('分页参数需为正整数，命令示例: sonar appName 50');
            return;
        }
        options.pageSize = pageSize;
    }

    const filteredArgs = appArg.split(/[, ]+/).filter(Boolean);

    if (filteredArgs.length !== 1) {
        logger.error('当前仅支持单个应用名，请移除多余的参数或分隔符');
        return;
    }

    // 1. 初始化
    const globalOpsFile = getGlobalOpsFile();
    let projectOpsFile;
    try {
        projectOpsFile = getProjectOpsFile();
    } catch (e) {
        // 忽略，可能不在项目目录
    }

    const client = new SonarQubeClient();

    // 2. 登录
    try {
        const user = globalOpsFile.read('ops_user');
        const password = globalOpsFile.read('ops_password');
        if (!user || !password) {
            throw new Error('未在全局配置中找到 ops_user 或 ops_password');
        }
        await client.login(user, password);
    } catch (e) {
        logger.error(`登录失败: ${e.message}`);
        return;
    }

    // 3. 解析应用名（默认仅支持单个应用）
    const appList = [];

    for (const rawName of filteredArgs) {
        const opsName = await resolveOpsName(rawName, projectOpsFile);
        appList.push({
            rawName,
            opsName
        });
    }

    await showUncoveredCode(client, appList[0].opsName, options.pageSize);
}
